#!/usr/bin/python 
import re
import sys
import ply.lex
from ply.lex import TOKEN


# not support 
#    pre-process directives, such as `include `ifdef `define, etc
#    comments // /**/
#    string " "

class VLexer(object): 
    """
    Lexer for Verilog language.
    """
    def __init__(self, error_func):
        self.error_func = error_func
    def build(self, **kwargs):
        """ 
        """
        self.lexer = ply.lex.lex(object=self, **kwargs)

    def reset_lineno(self):
        """ Resets the internal line number counter of the lexer.
        """
        self.lexer.lineno = 1

    def input(self, text):
        self.lexer.input(text)
    
    def token(self):
        g = self.lexer.token()
        return g


    ############################################################################
    ## 
    ############################################################################

    t_ignore = ' \t'

    # Newlines
    def t_NEWLINE(self, t):
        r'\n+'
        t.lexer.lineno += t.value.count("\n")


    keywords = (
        'ALWAYS',
        'AND',
        'ASSIGN',
        'AUTOMATIC',
        'BEGIN',
        'BUF',
        'BUFIF0',
        'BUFIF1',
        'CASE',
        'CASEX',
        'CASEZ',
        'CELL',
        'CMOS',
        'CONFIG',
        'DEASSIGN',
        'DEFAULT',
        'DEFPARAM',
        'DESIGN',
        'DISABLE',
        'EDGE',
        'ELSE',
        'END',
        'ENDCASE',
        'ENDCONFIG',
        'ENDFUNCTION',
        'ENDGENERATE',
        'ENDMODULE',
        'ENDPRIMITIVE',
        'ENDSPECIFY',
        'ENDTABLE',
        'ENDTASK',
        'EVENT',
        'FOR',
        'FORCE',
        'FOREVER',
        'FORK',
        'FUNCTION',
        'GENERATE',
        'GENVAR',
        'HIGHZ0',
        'HIGHZ1',
        'IF',
        'IFNONE',
        'INITIAL',
        'INOUT',
        'INPUT',
        'INSTANCE',
        'INTEGER',
        'JOIN',
        'LARGE',
        'LIBLIST',
        'LOCALPARAM',
        'MACROMODULE',
        'MEDIUM',
        'MODULE',
        'NAND',
        'NEGEDGE',
        'NMOS',
        'NOR',
        'NOSHOWCANCELLED',
        'NOT',
        'NOTIF0',
        'NOTIF1',
        'OR',
        'OUTPUT',
        'PARAMETER',
        'PMOS',
        'POSEDGE',
        'PRIMITIVE',
        'PULL0',
        'PULL1',
        'PULLDOWN',
        'PULLUP',
        'PULSESTYLE_ONEVENT',
        'PULSESTYLE_ONDETECT',
        'RCMOS',
        'REAL',
        'REALTIME',
        'REG',
        'RELEASE',
        'REPEAT',
        'RNMOS',
        'RPMOS',
        'RTRAN',
        'RTRANIF0',
        'RTRANIF1',
        'SCALARED',
        'SHOWCANCELLED',
        'SIGNED',
        'SMALL',
        'SPECIFY',
        'SPECPARAM',
        'STRONG0',
        'STRONG1',
        'SUPPLY0',
        'SUPPLY1',
        'TABLE',
        'TASK',
        'TIME',
        'TRAN',
        'TRANIF0',
        'TRANIF1',
        'TRI',
        'TRI0',
        'TRI1',
        'TRIAND',
        'TRIOR',
        'TRIREG',
        'USE',
        'UWIRE',
        'VECTORED',
        'WAIT',
        'WAND',
        'WEAK0',
        'WEAK1',
        'WHILE',
        'WIRE',
        'WOR',
        'XNOR',
        'XOR',
        'SETUPHOLD',
        'SETUP',
        'HOLD',
        'RECOVERY',
        'REMOVAL',
        'RECREM',
        'TIMESKEW',
        'FULLSKEW',
        'PERIOD',
        'WIDTH',
        'NOCHANGE',
        'SKEW',
        'PATHPULSE',
    )

    keyword_map = {}
    for r in keywords:
        keyword_map[r.lower()] = r

    tokens = keywords + (
    
        "TRIGGER",
        "STRING",
        'NUMBER',
        "PONG",
        "ADD",
        "MINUS",
        "POWER",
        "MUL",
        "DIV",
        "PER",
        "SLSHIFT",
        "SRSHIFT",
        "ULSHIFT",
        "URSHIFT",
        "LE",
        "LT",
        "GE",
        "GT",
        "CNE",
        "NE",
        "LNEG",
        "CEQ",
        "EQ",
        "TAND",
        "LAND",
        "BAND",
        "RNAND",
        "NXOR",
        "RNOR",
        "BNEG",
        "XORN",
        "BXOR",
        "LOR",
        "BOR",
        "QUESTION",
        "AT",

        # Delimeters
        "LPAREN",
        "RPAREN",
        "LBRACKET",
        "RBRACKET",
        "LBRACE",
        "RBRACE",
        "COMMA",
        "DOT",
        "SEMI",
        "COLON",

        # assignment
        "ASSIGNMENT",
        "DOLAR",
        'ESCAPE',



        'ID',

    )
    ############################################################################
    ## 
    ############################################################################
    z_digit                  = r'[zZ?]'
    x_digit                  = r'[xX]'
    hex_digit                = r'('+x_digit+'|'+z_digit+'|[0-9a-fA-F])'
    octal_digit              = r'('+x_digit+'|'+z_digit+'|[0-7])'
    binary_digit             = r'('+x_digit+'|'+z_digit+'|[01])'
    decimal_digit            = r'[0-9]'
    non_zero_decimal_digit   = r'[1-9]'
                             
    hex_base                 = r"'[sS]?[hH]"
    octal_base               = r"'[sS]?[oO]"
    binary_base              = r"'[sS]?[bB]"
    decimal_base             = r"'[sS]?[dD]"
                             
    hex_value                = hex_digit+r"("+hex_digit+"|_)*"
    octal_value              = octal_digit+r"("+octal_digit+"|_)*"
    binary_value             = binary_digit+r"("+binary_digit+"|_)*"
    unsigned_number          = decimal_digit+r"("+decimal_digit+"|_)*"
    non_zero_unsigned_number = non_zero_decimal_digit+"("+decimal_digit+"|_)*"

    size                     = non_zero_decimal_digit+'+'
    sign                     = "[+-]"
    hex_number               = "("+size+")?"+hex_base+hex_value
    octal_number             = "("+size+")?"+octal_base+octal_value
    binary_number            = "("+size+")?"+binary_base+binary_value
    exp                      = "[eE]"
    real_number              = "("+unsigned_number+"\."+unsigned_number+"|"+unsigned_number+"(\."+unsigned_number+")?"+exp+"("+sign+")?"+unsigned_number+")"
    decimal_number           = "(("+size+")?"+decimal_base+unsigned_number+"|"+unsigned_number+")" 

    number                   = "("+real_number+"|"+octal_number+"|"+binary_number+"|"+hex_number+"|"+decimal_number+")"

    
            

    identifier = r'[a-zA-Z_][0-9a-zA-Z_]*\b'

    @TOKEN(r'//[^\n]*')
    def t_SINGLE_COMMENT(self,t):
        pass
    @TOKEN(r'/\*[\d\D]*?\*/')
    def t_MUL_COMMENT(self,t):
        pass 
    @TOKEN(r'".*?"')
    def t_STRING(self,t):
        pass
    @TOKEN(r'`[^\n]*')
    def t_PP(self,t):
        pass
    @TOKEN(number)
    def t_NUMBER(self, t):
        return t

    @TOKEN(identifier)
    def t_ID(self, t):
        t.type = self.keyword_map.get(t.value, "ID")
        return t

    # Operators
    t_PONG              = r'\#'
    t_ADD               = r'\+'
    t_TRIGGER           = r'->'
    t_MINUS             = r'-'
    t_POWER             = r'\*\*'
    t_MUL               = r'\*'
    t_DIV               = r'/'
    t_PER               = r'%'
    t_SLSHIFT           = r'<<<'
    t_SRSHIFT           = r'>>>'
    t_ULSHIFT           = r'<<'
    t_URSHIFT           = r'>>'
    t_LE                = r'<='
    t_LT                = r'<'
    t_GE                = r'>='
    t_GT                = r'>'
    t_CNE               = r'!=='
    t_NE                = r'!='
    t_LNEG              = r'!'
    t_CEQ               = r'==='
    t_EQ                = r'=='
    t_TAND              = r'&&&'
    t_LAND              = r'&&'
    t_BAND               = r'&'
    t_RNAND             = r'\~&'
    t_NXOR              = r'\~\^'
    t_RNOR              = r'\~\|'
    t_BNEG               = r'\~'
    t_XORN              = r'\^\~'
    t_BXOR               = r'\^'
    t_LOR               = r'\|\|'
    t_BOR                = r'\|'
    t_QUESTION          = r'\?'
    t_AT                = r'@'

    # Delimeters
    t_LPAREN            = r'\('
    t_RPAREN            = r'\)'
    t_LBRACKET          = r'\['
    t_RBRACKET          = r'\]'
    t_LBRACE            = r'\{'
    t_RBRACE            = r'\}'
    t_COMMA             = r','
    t_DOT               = r'\.'
    t_SEMI              = r';'
    t_COLON             = r':'

    # assignment
    t_ASSIGNMENT         = r'='
    t_DOLAR              = r'\$'
    t_ESCAPE             = r'\\'

        
    def t_error(self, t):
        msg = 'Invalid chracter %s'%repr(t.value[0])
        self._error(msg,t)

    def _error(self, msg, token):
        location = self._make_tok_location(token)
        self.error_func(msg, location[0], location[1])
        self.lexer.skip(1)
    
    def _find_tok_column(self, token):
        i = token.lexpos
        while i > 0:
            if self.lexer.lexdata[i] == '\n': break
            i -= 1
        return (token.lexpos - i) + 1
    
    def _make_tok_location(self, token):
        return (token.lineno, self._find_tok_column(token))

if __name__=='__main__':
    def err_func(msg, a, b):
        print msg
        sys.exit(1)
    text = """
    & and or |       xkguo ab123 +19 -21 * / ^ ~^ ** 15:0 'h23 12'SH139 3'b010 5'o567 2'd89 'd135 87 123 3.14 23e-35 .xkguo module begin ifdef "hello this is me" $mytask
    //
        module a (p0, p1);
            input p0;
            output p1;
            initial begin
                $display("hello"):
            end
            always@(posedge p0 or negedge p1)begin
                p1 <= 0;
                p0 <= p1;
                /* xkguo 
                    Thisiscommentmultiline
                */
                `include "me.txt"
                `ifdef me
                `else
                `endif
            end
        endmodule
    """
    mylexer = VLexer(err_func)
    mylexer.build()
    mylexer.input(text)
    while 1:
        tok = mylexer.token()
        if not tok:
            break
        print "-", tok.value, tok.type, tok.lineno, tok.lexpos

